package cn.zwx.common.limiter.aspect;

import cn.zwx.base.enums.ResponseCodeEnum;
import cn.zwx.base.exception.BizWrapException;
import cn.zwx.common.limiter.annotation.LimitRule;
import cn.zwx.common.limiter.enums.LimiterTypeEnum;
import cn.zwx.common.limiter.service.LimiterService;
import cn.zwx.common.limiter.service.MemoryLimiterServiceImpl;
import cn.zwx.common.limiter.service.RedisLimiterServiceImpl;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;

/**
 * @author zhangwenxue
 */
@Aspect
@Order(0)
@Component
public class LimitAspect {

    public static final Logger logger = LoggerFactory.getLogger(LimitAspect.class);

    public static final String APPLICATION_NAME = "spring.application.name";

    @Resource
    private ApplicationContext context;


    @Around("@annotation(rule)")
    public Object around(ProceedingJoinPoint pjp, LimitRule rule) throws Throwable {
        String namespace = rule.namespace();
        if (ObjectUtils.isEmpty(namespace)){
            namespace = context.getEnvironment().getProperty(APPLICATION_NAME);
        }
        String key = namespace + ":" + rule.key();
        LimiterService limiter;
        LimiterTypeEnum type = LimiterTypeEnum.of(rule.type());
        if (type == LimiterTypeEnum.redis) {
            limiter =   context.getBean(RedisLimiterServiceImpl.class);
        } else {
            limiter =  context.getBean(MemoryLimiterServiceImpl.class);
        }
        try {
            if (!limiter.take(rule)) {
                logger.error("当前接口:{},在：{}秒内 访问过多",key,rule.period());
                throw new BizWrapException(ResponseCodeEnum.PERIOD_LIMITER_ERROR);
            }
            return pjp.proceed();
        }catch (Exception e){
            // 如果是业务异常直接抛出
            if ( e instanceof  BizWrapException){
                throw e;
            }
           if (type == LimiterTypeEnum.redis){
               logger.error("如果Redis限流异常退化成本地限流",e);
               // 退化为本地限流
               limiter =  context.getBean(MemoryLimiterServiceImpl.class);
               if (!limiter.take(rule)) {
                   logger.error("Redis限流异常,退化为本地限流 当前接口:{},在：{}秒内 访问过多",key,rule.period());
                   throw new BizWrapException(ResponseCodeEnum.PERIOD_LIMITER_ERROR);
               }
           }
        }
        return pjp.proceed();
    }
}
